debounce 都提到了,怎麼能不提 throltte 呢?

在玩 Pokemon Go 打對戰的時候,有時候想著按快一點把對方打趴,殊不知再怎麼快,你的神奇寶貝似乎也快不上你。
其他遊戲也有類似情境,例如點擊時角色會進行攻擊,按快一點角色還是會乖乖跑完第一下的動畫,等動畫完成之後,等你按「真正」的第二下才會繼續動作 (儘管前面按了三千多下),或像是連續點擊的技能,每點一下會有1秒的CD光圈跑完才能繼續按第二下,CD時間點擊了也不會有任何反應。
但有時候戰況激烈時,為了拚那 0.1 秒的死活真的是會不停瘋狂的按,為了這一刻,我們來製作一個點擊練習器吧!
刻意練習 throlttle (X)
製作無意義的功能 (O)
首先,我們需要一個視覺化的量表,Chakra-UI 有 <CircularProgress /> ,將它小小改造一下變成可點擊的按鈕。

(好的,看起來很欠按)
期望是:
首先,先把點擊功能製作出來,大概的樣子:
function Example() {
  const [value, setValue] = useState(0)
  const handleClick = () => {
    setValue((prev) => {
      const nextValue = prev + 1
      return nextValue >= 100 ? 100 : nextValue
    })
  }
  return (
    <Stack align="center">
      <CircularProgress value={value} onClick={handleClick} />
    </Stack>
  )
}
function useThrottle(cb, time = 500) {
  const isEnableRef = useRef(true)
  return (...args) => {
    if (!isEnableRef.current) {
      return
    }
    isEnableRef.current = false
    cb(...args)
    setTimeout(() => {
      isEnableRef.current = true
    }, time)
  }
}
與 debounce 不同在於,throltte 是立即觸發,而等到 isEnableRef 為 true 時,才允許下一次的執行,而 debounce 則是只要一直觸發持續延遲執行。
一樣是使用了 useRef 與 setTimeout 搭配組合,當觸發執行回傳的 function 時,isEnableRef 會變為 true,一直到 setTimeout 跑完給定的秒數時,才會變回 false,這時 function 就可以再執行一次 (準確來說,function 是可以一直被執行,但因 isEnableRef 為 false 的關係則 early return,當變回 true 時,就能順利執行後續的程式)。
| Param | Type | Description | 
|---|---|---|
cb | 
function | callback,當 setTimeout 的 time 時間到時會更改 isEnableRef 的狀態 | 
time | 
number | 單位ms,  定義 setTimeout 要 time 多久,default 為 500ms | 
| Return | Type | Description | 
|---|---|---|
() => {} | 
function | 執行的event,其中傳入的參數會進一步傳入一開始的cb | 
我們把原本的 setValue 傳入 useThrottle,並將回傳的 throttledEvent 替換原本 setValue 所放置的位置,也另外加上 <Timer/> 跟重置按鈕。
function Example() {
  const [value, setValue] = useState(0)
  const throttledEvent = useThrottle(setValue, 450)
  const handleClick = () => {
    throttledEvent((prev) => {
      const nextValue = prev + 1
      return nextValue >= 100 ? 100 : nextValue
    })
  }
  return (
    <Stack align="center">
      <Timer value={value} />
      <CircularProgress value={value} onClick={handleClick} />
      <Button onClick={() => setValue(0)} disabled={value !== 100}>
        RESET
      </Button>
    </Stack>
  )
}
然後就完成啦!

恩,手指好酸